<?php

namespace App\Http\Library\WeChat;

use EasyWeChat\Kernel\Http\StreamResponse;
use Exception;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

/**
 *
 */
class MiniWeChat
{
    /**
     * @var MiniWeChat|null
     */
    private static ?MiniWeChat $instance = null;

    /**
     * 小程序app应用
     * @var array
     */
    private $app = null;

    /**
     * 小程序支付对象
     * @var array
     */
    private $payment = null;

    /**
     * __construct 实例化
     */
    private function __construct()
    {
        $this->app = app('wechat.mini_program');
        $this->payment = app('wechat.payment');
    }

    /**
     * instance 单列化
     * @return MiniWeChat
     */
    static public function instance(): ?MiniWeChat
    {
        if (!self::$instance instanceof self) {
            self::$instance = new static();
        }
        return self::$instance;
    }

    /**
     * 小程序app对象
     * @return array|Application|mixed
     */
    public function getApp(){
        return $this->app;
    }

    /**
     * 支付对象
     * @return array|Application|mixed
     */
    public function getPayment(){
        return $this->payment;
    }

    /**
     * 微信订单
     * @param array $params
                [
                    'body' => '支付信息',
                    'out_trade_no' => '订单号',
                    'total_fee' => '支付金额,单位分',
                    'spbill_create_ip' => 'ip地址', // 可选，如不传该参数，SDK 将会自动获取相应 IP 地址
                    'notify_url' => '回调通知地址', // 支付结果通知网址，如果不设置则会使用配置里的默认地址
                    'trade_type' => 'JSAPI', // 请对应换成你的支付方式对应的值类型
                    'openid' => 'oUpF8uMuAJO_M2pxb1Q9zNjWeS6o',
                ]
     * @param bool $isContract
     * @return mixed
            {
                "return_code": "SUCCESS",
                "return_msg": "OK",
                "appid": "",
                "mch_id": "",
                "nonce_str": "",
                "openid": "",
                "sign": "",
                "result_code": "SUCCESS",
                "prepay_id": "",
                "trade_type": "JSAPI"
            }
     */
    public function pay(array $params = [], bool $isContract = false ){
        return $this->payment->order->unify($params,$isContract);
    }


    /**
     */
    public function getPayJsSdk($prepay_id,$bool = false){
        return $this->payment->jssdk->bridgeConfig($prepay_id, $bool); // 返回数组
    }

    /**
     * 获取手机号
     * @param string $code
     * @return mixed
     */
    public function getUserPhoneNumber(string $code): mixed
    {
        return $this->app->phone_number->getUserPhoneNumber($code);
    }


    /**
     * 提现请求
     * @param  [type] $order_id [商户订单号]
     * @param  [type] $openid   [用户openid]
     * @param  [type] $amount   [金额]
     * @param  [type] $desc     [说明信息]
     * @return [type]           [description]
     */
    public function toBalance($order_id,$openid,$amount,$desc){
        return $this->payment->transfer->toBalance([
            'partner_trade_no' => $order_id, // 商户订单号，需保持唯一性(只能是字母或者数字，不能包含有符号)
            'openid' => $openid,
            'check_name' => 'NO_CHECK', // NO_CHECK：不校验真实姓名, FORCE_CHECK：强校验真实姓名
            // 're_user_name' => '王小帅', // 如果 check_name 设置为FORCE_CHECK，则必填用户真实姓名
            'amount' => $amount * 100, // 企业付款金额，单位为分
            'desc' => $desc?$desc:"佣金提现", // 企业付款操作说明信息。必填
        ]);
    }

    /**
     * 退款
     * @param $transactionId
     * @param $refundNumber
     * @param $totalFee
     * @param $refundFee
     * @param array $optional //其他属性 ['refund_desc'=>'']
     */
    public function byTransactionId($transactionId,$refundNumber,$totalFee,$refundFee,array $optional = [])
    {
        // 参数分别为：微信订单号、商户退款单号、订单金额、退款金额、其他参数
        return $this->payment->refund->byTransactionId($transactionId, $refundNumber, $totalFee, $refundFee,$optional);
    }

    /**
     * 回调通知
     * @param callable $callback
     */
    public function notify(callable $callback){
        return $this->payment->handlePaidNotify($callback);
    }

    /**
     * 查询订单
     * @param $order_id
     */
    public function queryByOrderNo($order_id){
        return $this->payment->order->queryByOutTradeNumber($order_id);
    }

    /**
     * 查询订单
     * @param $order_id
     */
    public function queryByOutTradeNumber($order_id){
        return $this->payment->refund->queryByOutTradeNumber($order_id);
    }

    /**
     * 退款操作
     * @param string $number //支付的单号
     * @param string $refundNumber //退款单号
     * @param int $totalFee //总金额
     * @param int $refundFee //退款金额
     * @param array $optional //其他属性 ['refund_desc'=>'']
     * @return
     */
    public function byOutTradeNumber($number,$refundNumber,$totalFee,$refundFee,$optional){
        return $this->payment->refund->byOutTradeNumber($number,$refundNumber,$totalFee,$refundFee,$optional);
    }



    /**
     * [getCode 微信小程序登陆]
     * @param  [string] $code [code参数]
     * @return  [string]       [description]
     */
    public function getCode($code){
        return $this->app->auth->session($code);
    }

    /**
     * @param string $fileName 文件名带后缀
     * @param string $scenePath
     * @param string $dir 存储目录
     * @param int $type 为0时候, $scenePath为 小程序路径, 1时候为小程序参数
     * @param string $page
     * @param array $optional
     * @return string
     */
    public function createCode(string $fileName,string $scenePath,string $dir = "codes",int $type = 0, string $page = "pages/index/index", array $optional = []): string
    {
        $dir = trim($dir,"/");
        if (!is_dir("./storage/".$dir)){
            mkdir("storage/".$dir,0755);
        }
        $codeUrl =  $dir."/".$fileName;
        $file_path = "storage/".$codeUrl;
        if (!file_exists("./".$file_path)){
            try {
                if ($type){
                    $optional = array_merge([
                        'page'=>$page,
                        "width"=>430,
                        "auto_color"=>false,
                        "line_color"=>["r" => 0,"g" => 0,"b" => 0],
                    ],$optional);
                    $this->getUnlimit($scenePath, $optional, "storage/".$dir, $fileName);
                }else{
                    $this->getQrCode($page."?".$scenePath, 280, "storage/".$dir, $fileName);
                }
                return $codeUrl;
            } catch (\Exception $e) {
                Log::error("######START生成二维码失败START######");
                Log::error($e->getMessage());
                Log::error("######END生成二维码失败END######");
                return "";
            }
        }else{
            return $codeUrl;
        }
    }

    /**
     * [getAppCode 获取数量较少的业务场景小程序码]
     * @param string $path [必选参数:小程序页面路径]
     * @param array $optional [可选参数数组][
     *                                    width Int - 默认 430 二维码的宽度
     *                                        auto_color 默认 false 自动配置线条颜色，如果颜色依然是黑色，则说明不建议配置主色调
     *                                        line_color 数组，auto_color 为 false 时生效，使用 rgb 设置颜色 例如 ，示例：["r" => 0,"g" => 0,"b" => 0]。]
     * @param string $to_path [保存的目标路径]
     * @param string $to_name [保存的图片名称,带后缀]
     * @return bool|int|string [type]           [description]
     * @throws Exception
     */
    public function getAppCode(string $path, array $optional = [], string $to_path = '', string $to_name = ''){
        if(empty($optional)){
            $response = $this->app->app_code->get($path);
        }else{
            $response = $this->app->app_code->get($path,$optional);
        }
        return $this->saveImage($response,$to_path,$to_name);
    }

    /**
     * [getUnlimit 适用于需要的小程序码数量极多，或仅临时使用的业务场景]
     * @param string $scene [必选参数]
     * @param array $optional [可选参数数组][
     *                                    page string 小程序页面路径
     *                                    width Int - 默认 430 二维码的宽度
     *                                        auto_color 默认 false 自动配置线条颜色，如果颜色依然是黑色，则说明不建议配置主色调
     *                                        line_color 数组，auto_color 为 false 时生效，使用 rgb 设置颜色 例如 ，示例：["r" => 0,"g" => 0,"b" => 0]。]
     * @param string $to_path [保存的目标路径]
     * @param string $to_name [保存的图片名称,带后缀]
     * @return bool|int|string [type]           [description]
     * @throws Exception
     */
    public function getUnlimit(string $scene, array $optional = [], string $to_path = '', string $to_name = ''){
        if(empty($optional)){
            $response = $this->app->app_code->getUnlimit($scene);
        }else{
            $response = $this->app->app_code->getUnlimit($scene,$optional);
        }
        return $this->saveImage($response,$to_path,$to_name);
    }

    /**
     * [getQrCode 适用于需要的小程序码数量极多，或仅临时使用的业务场景]
     * @param string $path [必选参数,扫码进入的小程序页面路径,最大长度 128 字节]
     * @param string $width [二维码的宽度，单位 px。最小 280px，最大 1280px]
     * @param string $to_path [保存的目标路径]
     * @param string $to_name [保存的图片名称,带后缀]
     * @return bool|int|string [array]           [description]
     * @throws Exception
     */
    public function getQrCode(string $path, string $width = null, string $to_path = '', string $to_name = ''){
        if($width===null){
            $response = $this->app->app_code->getQrCode($path);
        }else{
            $response = $this->app->app_code->getQrCode($path,$width);
        }
        return $this->saveImage($response,$to_path,$to_name);
    }

    /**
     * [saveImage 保存图片]
     * @param string $response [小程序码获取对象]
     * @param string $to_path [保存的目标路径]
     * @param string $to_name [保存的图片名称,带后缀]
     * @return bool|int|string 返回保存路径
     * @throws Exception
     */
    protected function saveImage($response,string $to_path = '', string $to_name = ''){
        //保存小程序码到文件
        if ($response instanceof StreamResponse) {
            if($to_name){
                $filename = $response->save($to_path,$to_name);
            }else{
                $filename = $response->save($to_path);
            }
            return $filename;
        }else{
            throw new Exception(json_encode($response,JSON_UNESCAPED_UNICODE));
        }
    }

    public function sendMiniTempolate($openid,$template_id, $data, $path = "pages/index/index"){
        $miniprogram = [
            "appid"=>env("WECHAT_MINI_PROGRAM_APPID"),
            "pagepath"=> $path,
        ];
        $appid = env("WECHAT_OFFICIAL_ACCOUNT_APPID");
        $accessToken = $this->app->access_token;
        $token = $accessToken->getToken();
        $url = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token={$token['access_token']}";
        $ret = Http::post($url,[
            'access_token' => $token['access_token'],
            'touser' => $openid,
            'mp_template_msg' => [
                "appid"=>$appid,
                'template_id' => $template_id,
                'miniprogram' => $miniprogram,
                'data'=>$data
            ],
        ]);
//        Log::info($ret);
        if (!$ret->ok()){
            Log::error("错误通知:");
            Log::error($ret->body());
        }
    }

    /**
     * [sendTemplate 模板消息发送]
     * @param array $optional [消息参数，参考]
     * @return  [array]           [description]
     */
    public function sendTemplate(array $optional){
        return $this->app->template_message->send($optional);
    }

    /**
     * [encryptor 微信加密数据的解密方法]
     * @param string $session [description]
     * @param string $iv [description]
     * @param string $encryptedData [description]
     * @return  [array]                [description]
     */
    public function encryptor(string $session,string $iv,string $encryptedData){
        return $this->app->encryptor->decryptData($session, $iv, $encryptedData);
    }

    /**
     * [checkText 用于校验一段文本是否含有违法内容]{单个appid调用上限为2000次/分钟，1,000,000次/天}
     * @param  string $content [description]
     * @return [array]          [description]
     */
    public function checkText(string $content){
        return $this->app->content_security->checkText($content);
    }

    /**
     * [data_cube 小程序概况趋势]
     * @param  string $type [概况趋势:summaryTrend,访问日趋势:dailyVisitTrend,访问周趋势:weeklyVisitTrend,访问月趋势
    :monthlyVisitTrend,....]详情请看https://www.easywechat.com/docs/4.1/mini-program/data_cube
     * @param  string $from [开始日期]
     * @param  string $to   [结束日期]
     * @return [array]       [description]
     */
    public function data_cube(string $type, string $from, string $to){
        return $this->app->data_cube->$type($from,$to);
    }


    /**
     * [__clone 防止克隆对象]
     */
    private function __clone(){
        // TODO: Implement __clone() method.
    }

    /**
     * 防止反序列化（这将创建它的副本）
     */
    public function __wakeup()
    {
        // TODO: Implement __wakeup() method.
    }
}
